#include "my_thread_base.h"
#include "my_log.h"
#include "my_str.h"

// protect global data in current source file
static my_mutex_t       guard                   = MY_MUTEX_INITIALIZER;
static my_bool_t        thread_local_key_inited = MY_FALSE;
static my_thread_key_t  thread_local_key;

typedef struct {
    my_rbtree_node_t    tree_node;
    const char          *name;
    void                *value;
} my_thread_store_item_t;

static int
my_thread_store_item_cmp(my_rbtree_node_t *a, my_rbtree_node_t *b)
{
    my_thread_store_item_t *aitem = (my_thread_store_item_t *)a;
    my_thread_store_item_t *bitem = (my_thread_store_item_t *)b;
    return strcmp(aitem->name, bitem->name);
}

static void
check_and_init_thread_local_key()
{
    my_mutex_lock(&guard);
    if (!thread_local_key_inited) {
        my_thread_key_create(&thread_local_key, NULL);
        thread_local_key_inited = MY_TRUE;
    }
    my_mutex_unlock(&guard);
}

static inline void *
my_thread_base_runner(void *arg)
{
    my_thread_base_t *thread_base = (my_thread_base_t *)arg;
    my_thread_setspecific(thread_local_key, thread_base);
    thread_base->execute_pt(thread_base);
    return NULL;
}

static void *
thread_malloc_in_global(int64_t size)
{
    return my_mem_pool_alloc(&g_mem_pool, size);
}

static void
thread_free_in_global(void *ptr)
{
    my_mem_pool_free(&g_mem_pool, ptr);
}

static void *
thread_aligned_malloc_in_global(int64_t alignment, int64_t size)
{
    (void)alignment;
    return my_4kaligned_mem_pool_alloc(&g_4kaligned_mem_pool, size);
}

static void
thread_aligned_free_in_global(void *ptr)
{
    my_4kaligned_mem_pool_free(&g_4kaligned_mem_pool, ptr);
}

void
my_thread_base_start(my_thread_base_t *thread_base,
    my_thread_base_execute_pt execute_pt, void *data,
    int64_t mem_pool_water_level, int64_t aligned_mem_pool_water_level)
{
    check_and_init_thread_local_key();
    thread_base->execute_pt = execute_pt;
    thread_base->data = data;

    if (mem_pool_water_level > 0) {
        thread_base->mem_pool = my_calloc(sizeof(my_mem_pool_t));
        my_mem_pool_init(thread_base->mem_pool,
            mem_pool_water_level,
            thread_malloc_in_global,
            thread_free_in_global);
    }

    if (aligned_mem_pool_water_level > 0) {
        thread_base->aligned_mem_pool = my_calloc(sizeof(my_4kaligned_mem_pool_t));
        my_4kaligned_mem_pool_init(thread_base->aligned_mem_pool,
            aligned_mem_pool_water_level,
            thread_aligned_malloc_in_global,
            thread_aligned_free_in_global);
    }

    my_rbtree_init(&thread_base->store_rbtree,
                    &thread_base->store_rbtree_sentinel,
                    my_thread_store_item_cmp);

    thread_base->tid = my_thread_create(my_thread_base_runner, thread_base);
}

void
my_thread_base_join(my_thread_base_t *thread_base, void *ret)
{
    my_thread_join(thread_base->tid, ret);

    my_rbtree_node_t        *node;
    my_thread_store_item_t  *item;

    while(!my_rbtree_empty(&thread_base->store_rbtree)) {
        node = my_rbtree_min(thread_base->store_rbtree.root,
            thread_base->store_rbtree.sentinel);
        my_rbtree_delete(&thread_base->store_rbtree, node);
        item = (my_thread_store_item_t *)node;
        my_free(item);
    }

    if (thread_base->mem_pool) {
        my_mem_pool_clean(thread_base->mem_pool);
        my_free(thread_base->mem_pool);
    }

    if (thread_base->aligned_mem_pool) {
        my_4kaligned_mem_pool_clean(thread_base->aligned_mem_pool);
        my_free(thread_base->aligned_mem_pool);
    }
}

my_thread_base_t *
current_thread_base()
{
    my_thread_base_t *thread_base = my_thread_getspecific(thread_local_key);
    return thread_base;
}

void *
my_thread_malloc(int64_t size)
{
    my_thread_base_t *thread_base = current_thread_base();
    if (thread_base != NULL && thread_base->mem_pool) {
        return my_mem_pool_alloc(thread_base->mem_pool, size);
    }

    return my_mem_pool_alloc(&g_mem_pool, size);
}

void *
my_thread_calloc(int64_t size)
{
    void *ptr = my_thread_malloc(size);
    bzero(ptr, (size_t)size);
    return ptr;
}

void
my_thread_free(void *ptr)
{
    my_thread_base_t *thread_base = current_thread_base();
    if (thread_base != NULL && thread_base->mem_pool) {
        return my_mem_pool_free(thread_base->mem_pool, ptr);
    }

    return my_mem_pool_free(&g_mem_pool, ptr);
}

void *
my_thread_aligned_malloc(int64_t size)
{
    my_thread_base_t *thread_base = current_thread_base();
    if (thread_base != NULL && thread_base->aligned_mem_pool) {
        return my_4kaligned_mem_pool_alloc(thread_base->aligned_mem_pool, size);
    }

    return my_4kaligned_mem_pool_alloc(&g_4kaligned_mem_pool, size);
}

void *
my_thread_aligned_calloc(int64_t size)
{
    void *ptr = my_thread_aligned_malloc(size);
    bzero(ptr, (size_t)size);
    return ptr;
}

void
my_thread_aligned_free(void *ptr)
{
    my_thread_base_t *thread_base = current_thread_base();
    if (thread_base != NULL && thread_base->aligned_mem_pool) {
        return my_4kaligned_mem_pool_free(thread_base->aligned_mem_pool, ptr);
    }

    return my_4kaligned_mem_pool_free(&g_4kaligned_mem_pool, ptr);
}

void
my_thread_store_put(const char *name, void *value)
{
    my_thread_base_t *thread_base = current_thread_base();
    if (thread_base == NULL) {
        MY_LOG_FATAL("unreachable");
    }

    my_thread_store_item_t *item = my_calloc(sizeof(my_thread_store_item_t));
    item->name = name;
    item->value = value;

    my_rbtree_insert(&thread_base->store_rbtree, &item->tree_node);
}

void
my_thread_store_safe_put(const char *name, void *value)
{
    my_thread_base_t *thread_base = current_thread_base();
    if (thread_base == NULL) {
        MY_LOG_FATAL("unreachable");
    }

    my_thread_store_item_t target = {
        .name = name,
    };

    my_rbtree_node_t *node = my_rbtree_lookup(&thread_base->store_rbtree, &target.tree_node);
    if (node != NULL) {
        MY_LOG_FATAL("%s object conflict", name);
    }

    my_thread_store_put(name, value);
}

void *
my_thread_store_get(const char *name)
{
    my_rbtree_node_t *node;

    my_thread_base_t *thread_base = current_thread_base();
    if (thread_base == NULL) {
        MY_LOG_FATAL("unreachable");
    }

    my_thread_store_item_t target = {
        .name = name,
    };

    node = my_rbtree_lookup(&thread_base->store_rbtree, &target.tree_node);
    if (node == NULL) {
        return NULL;
    }

    my_thread_store_item_t *item = (my_thread_store_item_t *)node;
    return item->value;
}

void
my_thread_store_del(const char *name)
{
    my_rbtree_node_t        *node;
    my_thread_store_item_t  *item;

    my_thread_base_t *thread_base = current_thread_base();
    if (thread_base == NULL) {
        MY_LOG_FATAL("unreachable");
    }

    my_thread_store_item_t target = {
        .name = name,
    };

    node = my_rbtree_lookup(&thread_base->store_rbtree, &target.tree_node);
    if (node == NULL) {
        return;
    }

    my_rbtree_delete(&thread_base->store_rbtree, node);

    item = (my_thread_store_item_t *)node;

    my_free(item);
}